package vistop.io.lisp;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;

import org.armedbear.lisp.Function;
import org.armedbear.lisp.Interpreter;
import org.armedbear.lisp.JavaObject;
import org.armedbear.lisp.Lisp;
import org.armedbear.lisp.LispObject;
import org.armedbear.lisp.Stream;
import org.armedbear.lisp.Symbol;

import vistop.datatypes.Data;
import vistop.datatypes.Edge;
import vistop.datatypes.Face;
import vistop.datatypes.GeometricalObject;
import vistop.datatypes.Vertex;
import vistop.datatypes.Volume;


public class LispParser {
	private Data data;
	
	
	private Logger logg = Logger.getLogger(
			LispParser.class.getPackage().getName());
	
	private boolean initialized; 
	private Stream lispStream;
	private Function read;
	private LispObject EOF;
		
	private void init(){
		if (initialized) return;

		Interpreter.createInstance();
		EOF = new LispObject();
		read = (Function) Symbol.READ.getSymbolFunction();
		initialized = true; 
		data = new Data();
	}
	
	
	/**
	 * Generates an instance of a LispParser from the given InputStream.
	 * 
	 * @param istr
	 */
	public LispParser(InputStream istr) {
		initialized = false; 
		init();
		try{
			lispStream = (Stream) JavaObject.getInstance(istr, true, Stream.class);
		}catch(Exception streamsNoYetHandled){
			// temporary workaround: TODO
			lispStream = new Stream(Symbol.STREAM, istr, Symbol.CHARACTER);
		}

	}
	
	public LispObject getObject(){
		LispObject result = read.execute(lispStream, Lisp.NIL, EOF, Lisp.NIL);
        return result == EOF ? null : result; 
	}
	
	public Data parseFile(InputStream data) {
		LispParser p = new LispParser(data);

		for (LispObject lo = p.getObject(); lo != null; 
				lo = p.getObject()) {
			MyLispObject mlo = new MyLispObject(lo);
			addGeometricalObject(mlo);
		}
		return this.data;
	}
	
	private GeometricalObject addGeometricalObject(MyLispObject mlo) {
		LispType name = mlo.getName();
		switch (name) {
		case VERTEX:
			return createVertex(mlo);
		case CELL:
			return createCell(mlo);
			//break;
		case BOUNDARY:
			return createBoundary(mlo);
		}
		System.out.println("This should not happen");
		return null;
	}
	
	private Vertex createVertex(MyLispObject mlo) {
		if (!mlo.getName().equals(LispType.VERTEX)) {
			// TODO: error handeling (this is not a vertex so this method should not be called)
			System.out.println("This is no vertex");
			return null;
		}
		int id = (int)mlo.getAttribute(AttributeName.ID);
		
		if (data.getVertices().containsKey(id)) {
			if (!data.getVertices().get(id).isIncomplete())
				System.out.println("Vertex with id " + id + "is incomplete");
				return null;
		} else {
			data.addVertex(new Vertex(id));
		}
		Vertex v = data.getVertices().get(id);
		
		double x = mlo.getAttribute(AttributeName.X);
		double y = mlo.getAttribute(AttributeName.Y);
		double z = mlo.getAttribute(AttributeName.Z);
		//v.setXYZ(x, y, z);
		data.getVertices().get(id).setXYZ(x, y, z);
		return data.getVertices().get(id);
	}
	
	private GeometricalObject createCell(MyLispObject mlo) {
		int id = (int)mlo.getAttribute(AttributeName.ID);
		int dim = (int)mlo.getAttribute(AttributeName.DIM);
		switch(dim) {
		case 0: //redundant with VERTEX entrys
			//break;
			return null;
		case 1:	// edge		
			data.addEdge(new Edge(id));
			return data.getEdges().get(id);
		case 2: // face
			data.addFace(new Face(id));
			return data.getFaces().get(id);
		case 3:	// volume
			data.addVolume(new Volume(id));
			return data.getVolumes().get(id);
		}
		System.out.println("problem with creating cell");
		return null;
	}
	
	private GeometricalObject createBoundary(MyLispObject mlo) {
		if (!mlo.getName().equals(LispType.BOUNDARY)) {
			// TODO
			System.out.println("not boundary");
			return null;
		}
		int id = (int)mlo.getAttribute(AttributeName.ID);
		int dim = (int)mlo.getAttribute(AttributeName.DIM);
		int childId = (int)mlo.getAttribute(AttributeName.BDID);
		int childDim = (int)mlo.getAttribute(AttributeName.BDDIM);
		int value = (int)mlo.getAttribute(AttributeName.VALUE);
		if (childDim != (dim -1)) {
			// TODO error handeling
			System.err.println("wrong child dim #id=" + id);
			return null;
		}
		
		switch(dim) {
		case 0:
			break;
		case 1: //edge
			data.addEdge(new Edge(id));
			data.addVertex(new Vertex(childId));
			Vertex v = data.getVertices().get(childId);
			if (value > 0)
				data.getEdges().get(id).setStart(v);
			else
				data.getEdges().get(id).setEnd(v);
			return data.getEdges().get(id);
		case 2: //face
			data.addFace(new Face(id));
			data.addEdge(new Edge(childId));
			Edge e = data.getEdges().get(childId);
			data.getFaces().get(id).addEdge(e, value);
			return data.getFaces().get(id);
		case 3: //volume
			data.addVolume(new Volume(id));
			data.addFace(new Face(childId));
			Face f = data.getFaces().get(childId);
			data.getVolumes().get(id).addFace(f, value);
			return data.getVolumes().get(id);
		}
		System.out.println("This shouldnt happen(boundary)");
		return null;
	}
	
}